package com.itheima.leetcode.od.b.logicalsimulation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * (B卷,100分)- 斗地主之顺子（Java & JS & Python）
 * <p>
 * 题目描述
 * <p>
 * 在斗地主扑克牌游戏中， 扑克牌由小到大的顺序为：3,4,5,6,7,8,9,10,J,Q,K,A,2，玩家可以出的扑克牌阵型有：单张、对子、顺子、飞机、炸弹等。
 * <p>
 * 其中顺子的出牌规则为：由至少5张由小到大连续递增的扑克牌组成，且不能包含2。
 * <p>
 * 例如：{3,4,5,6,7}、{3,4,5,6,7,8,9,10,J,Q,K,A}都是有效的顺子；而{J,Q,K,A,2}、 {2,3,4,5,6}、{3,4,5,6}、{3,4,5,6,8}等都不是顺子。
 * <p>
 * 给定一个包含13张牌的数组，如果有满足出牌规则的顺子，请输出顺子。
 * <p>
 * 如果存在多个顺子，请每行输出一个顺子，且需要按顺子的第一张牌的大小（必须从小到大）依次输出。
 * <p>
 * 如果没有满足出牌规则的顺子，请输出No。
 * <p>
 * 输入描述
 * <p>
 * 13张任意顺序的扑克牌，每张扑克牌数字用空格隔开，每张扑克牌的数字都是合法的，并且不包括大小王：2 9 J 2 3 4 K A 7 9 A 5 6
 * <p>
 * 不需要考虑输入为异常字符的情况
 * <p>
 * 输出描述
 * 组成的顺子，每张扑克牌数字用空格隔开：3 4 5 6 7
 * <p>
 * 用例
 * <p>
 * 输入	2 9 J 2 3 4 K A 7 9 A 5 6
 * <p>
 * 输出	3 4 5 6 7
 * <p>
 * 说明	13张牌中，可以组成的顺子只有1组：3 4 5 6 7。
 * <p>
 * 输入	2 9 J 10 3 4 K A 7 Q A 5 6
 * <p>
 * 输出
 * <p>
 * 3 4 5 6 7
 * <p>
 * 9 10 J Q K A
 * <p>
 * 说明	13张牌中，可以组成2组顺子，从小到大分别为：3 4 5 6 7 和 9 10 J Q K A
 * <p>
 * 输入	2 9 9 9 3 4 K A 10 Q A 5 6
 * <p>
 * 输出	No
 * <p>
 * 说明	13张牌中，无法组成顺子。
 * <p>
 * 题目解析
 * <p>
 * 先看一个例子：
 * <p>
 * 3 4 5 6 6 7 7 8 9 10
 * <p>
 * 这个例子该输出什么呢？
 * <p>
 * 如果是优先最长顺子的话，那么应该输出：
 * <p>
 * 3 4 5 6 7 8 9 10
 * <p>
 * 如果是优先最多顺子的话，那么应该输出：
 * <p>
 * 3 4 5 6 7
 * <p>
 * 6 7 8 9 10
 * <p>
 * 下面提供两种解法，分别实现优先最长顺子解法，和优先最多顺子解法。
 * <p>
 * 2023.07.10
 * <p>
 * 之前有考友在评论区反馈 优先最长顺子解法的通过率是80%，今天评论区又有一个考友提供了一个用例：
 * <p>
 * 3 4 5 6 7 3 4 5 6 7 A A A
 * <p>
 * 之前，优先最长顺子解法是基于栈结构实现的，在测试该用例时，产生死循环。因此我怀疑20%未通过的用例就是类似于这个。
 * <p>
 * 现在我对优先最长顺子解法做了更新优化，已支持该类型用例，大家考试时可以尝试使用优先最长顺子解法。
 * <p>
 * 优先最长顺子解法
 * 我的解题思路如下：
 * <p>
 * 首先定义一个mapToNum方法，即将字符串牌面 映射为 数值。具体映射关系可看代码中mapToNum方法的实现。
 * <p>
 * 然后，将输入的牌数组，按照mapToNum映射的数值，进行升序排序。
 * <p>
 * 之后，我创建多个顺子容器，外层遍历牌数组，内层遍历顺子容器：
 * <p>
 * 如果顺子容器是空的，则直接将当前牌加入该顺子容器
 * <p>
 * 如果顺子容器不是空的，则比较当前牌cur 和 顺子容器尾牌tail
 * <p>
 * 如果cur - tail = 1，则当前牌cur可以加入当前顺子尾部。
 * <p>
 * 如果cur - tail != 1，则当前牌cur不能加入当前顺子尾部，我们应该继续尝试将cur拼接到下一个顺子容器的尾部。
 * <p>
 * 按照上面逻辑，我们最后可以得到多个顺子容器，我们需要过滤掉其中长度小于5的顺子，如果过滤完后没有了，则打印输出“No”，如果过滤完后还有，则剩余顺子容器按照 第一张牌数值 从小到大排序后，依次打印。
 */
public class StraightCards {
    public static void main(String[] args) {
        /*Scanner sc = new Scanner(System.in);
        String[] cards = sc.nextLine().split(" ");*/

        String input = "2 9 J 2 3 4 K A 7 9 A 5 6";
        String[] cards = input.split(" ");

        System.out.println(getResult(cards));
        System.out.println(getResult2(cards));
    }

    public static String getResult(String[] cards) {
        Arrays.sort(cards, (a, b) -> mapToNum(a) - mapToNum(b));

        ArrayList<LinkedList<String>> straights = new ArrayList<>();
        straights.add(creatStraight(cards[0]));

        outer:
        for (int i = 1; i < cards.length; i++) {
            String card = cards[i];

            for (LinkedList<String> s : straights) {
                if (mapToNum(card) - mapToNum(s.getLast()) == 1) {
                    s.add(card);
                    continue outer;
                }
            }

            straights.add(creatStraight(card));
        }

        List<LinkedList<String>> collect = straights.stream()
                .filter(straight -> straight.size() >= 5)
                .collect(Collectors.toList());

        if (collect.size() == 0) {
            return "No";
        }

        return collect.stream()
                .sorted((a, b) -> mapToNum(a.getFirst()) - mapToNum(b.getFirst()))
                .map(list -> list.stream()
                        .collect(Collectors.joining(" ")))
                .collect(Collectors.joining("\n"));
    }

    /**
     * 双指针
     *
     * @param cards
     * @return
     */
    public static String getResult2(String[] cards) {
        Arrays.sort(cards, (a, b) -> mapToNum(a) - mapToNum(b));

        ArrayList<LinkedList<String>> collect = new ArrayList<>();
        LinkedList<String> straight = null;
        for (int i = 0; i <= cards.length - 5; i++) {
            straight = new LinkedList<>();
            straight.add(cards[i]);
            for (int j = i + 1; j < cards.length; j++) {
                if (mapToNum(cards[j]) - mapToNum(cards[j - 1]) == 1) {
                    straight.add(cards[j]);
                } else {
                    if (5 <= straight.size()) {
                        collect.add(straight);
                    }
                    break;
                }
            }
        }

        // 收尾操作
        if (null != straight && 5 <= straight.size()) {
            collect.add(straight);
        }

        if (collect.size() == 0) {
            return "No";
        }

        return collect.stream()
                .sorted((a, b) -> mapToNum(a.getFirst()) - mapToNum(b.getFirst()))
                .map(list -> list.stream()
                        .collect(Collectors.joining(" ")))
                .collect(Collectors.joining("\n"));
    }

    public static LinkedList<String> creatStraight(String card) {
        LinkedList<String> straight = new LinkedList<>();
        straight.add(card);
        return straight;
    }

    /**
     * 斗地主点数规则
     *
     * @param card
     * @return
     */
    public static int mapToNum(String card) {
        switch (card) {
            case "J":
                return 11;
            case "Q":
                return 12;
            case "K":
                return 13;
            case "A":
                return 14;
            case "2":
                return 16;
            default:
                return Integer.parseInt(card);
        }
    }
}